
#pragma once

// #include <cctype>
#include <cmath>
#include <cstdint>
#include <limits>

#include "oled_basic_font.hpp"
#include "oled_bitmap.hpp"
#include "oled_common.hpp"
#include "oled_include_config.hpp"
#include "oled_include_pgmspace.hpp"
#include "oled_interface.hpp"

// DEBUG
// #include "Arduino.h"


namespace oled_basic {

    enum class number_base {
        n10 = 10,
        n16 = 16,
        n2 = 2,

        dec = 10,
        hex = 16,
        bin = 2,
    };


    enum class ink {
        non_inverse,
        inverse,
        none,
    };


/**
 * @brief 无缓冲单色、灰阶屏绘图类，只支持按页绘图
 *
 * 这个类不追求支持所有种类的单色点阵屏幕，不像U8G2 那样大而全。
 * 除了要求屏幕要内置显存，还有一些其他要求。
 *
 * 像素组织方式：
 *
 * 单色OLED 或LCD 屏幕的显存的组织方式和SSD1306 系列相同。
 * 对于灰阶屏，则要求组织方式和ST7571 或SSD1328 相同。
 *
 * @tparam Backend
 */
#ifdef __cpp_concepts
    template <BackendFeatureSet Backend>
#else
    template <typename Backend>
#endif
    class MonoBasic {
       private:
        const FontMeta *_font;
        Backend *_drv;

        size_t _character_size;  // 字体里每个字符数据的字节数

        float _multiply_factor = 100.0;  // 用于将浮点数转换为整数的乘数
        float _round_bit = 0.5 / 100;    // 用于四舍五入的常数

        PixelIndexType _max_column;
        PixelIndexType _max_page;

        PixelIndexType _current_column;
        PixelIndexType _current_page;

        number_base _current_num_base = number_base::n10;

        ink _current_ink = ink::non_inverse;

        uint8_t _precision = 2;

        // put_unsigned 转换数字为字符串时使用的缓冲区大小
        // 需要1 字节放置\0，1 字节放置负号等前缀，所以要 + 2
        static constexpr size_t NUMBER_BUFFER_SIZE = NUMBER_LITERAL_WIDTH + 2;
        char _number_buffer[NUMBER_BUFFER_SIZE] = {0};

#if _OLED_BASIC_ENABLE_GRAYSCALE > 0
        uint8_t _screen_grayscale_bit;  // 屏幕的灰阶bit 数
        uint8_t _max_grayscale_level;   // 灰阶等级数 == 2^(_grayscale_bit;)

        // 在灰阶屏幕上显示单色内容时，可以设置要将单色内容变换到哪个灰阶等级
        uint8_t _current_grayscale_level;
#endif

        // 显示数字时，数字在屏幕上占用的字符长度。当_num_length 为负，对应左对齐，否则为右对齐，
        // 绝对值表示字符长度，最大不超过NUMBER_BUFFER_SIZE。
        // 左对齐时，在数字右侧填充字符，使总长度等于_num_length；右对齐则在数字左侧填充。
        // 数字本身长度大于总长度时，无法右对齐。
        int8_t _num_length = 0;
        char _num_filling_char = ' ';  // 对齐数字时要填充的字符，默认空格

        uint8_t _char_spacing = 0;  // 在字符后附加的间隔

        PixelIndexType _column_correct(PixelIndexType col) const {
            if (col > _max_column) {
                return _max_column;
            }
            else {
                return col;
            }
        }

        PixelIndexType _page_correct(PixelIndexType page) const {
            if (page > _max_page) {
                return _max_page;
            }
            else {
                return page;
            }
        }

        void _update_cursor(PixelIndexType col_count, PixelIndexType page_count) {
            auto start_col = _current_column;
            // auto start_page = _current_page;

            auto end_col = start_col + col_count;
            // auto end_page = start_page + page_count - 1;  // 一个page 有很多column，从0 page 开始写1 page 并不一定会写满

            _current_column = _column_correct(end_col);
            // _current_page = _page_correct(end_page);
        }

        /**
         * @brief 将一字节显示数据扩展为灰度序列，发送给屏幕。
         *
         * 如果屏幕是单色的，则不需要扩展灰度。
         * 必须先调用begin_tx。
         *
         * 将单色显示数据转换成灰度序列，原理参考：https://blog.csdn.net/Etberzin/article/details/143701923
         *
         */
        void _send_grayscale_sequence(uint8_t data) const {
#if _OLED_BASIC_ENABLE_GRAYSCALE > 0
            if (_screen_grayscale_bit > 1) {
                auto grayscale_mask = ~_current_grayscale_level;
                for (auto g = _screen_grayscale_bit; g > 0; --g) {
                    uint_fast8_t d;

                    if (grayscale_mask & 0x01) {
                        d = data;
                    }
                    else {
                        d = 0;
                    }

                    _drv->write(d);
                    grayscale_mask >>= 1;
                }
            }
            else {
                _drv->write(data);
            }
#else
            _drv->write(data);
#endif
        }

        /**
         * @brief 将一组字体或bitmap 数据发送到屏幕
         *
         * “一组数据”是指对应一列8 个像素的显示数据，如果是单色屏，就是1 字节；
         * 是灰阶屏，就是对应灰阶数量的多个字节。
         *
         * 必须先调用begin_tx()。
         *
         * 不同灰阶数据的处理策略:
         *
         * - 显示数据的灰阶和屏幕相同，则直接将对应灰阶数的数据写入屏幕；
         * - 数据灰阶大于1，但小于屏幕，将在显示数据的低位补0；
         * - 数据灰阶大于屏幕，将高地址的部分数据发给屏幕，字节数满足屏幕的灰阶
         * - 数据灰阶等于1，屏幕灰阶大于1，执行灰阶序列扩展算法；
         *
         * 低位补0：
         *
         * 就是多发几个0x00 字节给屏幕，把不足的数据补上
         * 如果显示数据灰阶是2bit，屏幕是3bit。数据里某个像素的灰阶是3，即0b11，低位补零后，这个像素变为0b110。
         *
         * PROGMEM：
         *
         * AVR 程序的字体数据一般都是放在PROGMEM 里的，操作方式和RAM 里的普通数据不同，
         * 所以不能把PROGMEM 里的字体数据当成RAM 里的bitmap，虽然只有一行代码不同。
         * 如果用一个函数参数来判断要不要以PROGMEM 方式读取数据，对于不用PROGMEM 的ARM 单片机而言，
         * 就是一个多余的条件判断；而且put_char 本来就在put_string 等其他函数调用链的末端，会被频繁调用，
         * 再增加一层_send_data，调用链就更长了，会有一些性能损失，还增加了爆栈的风险。
         * 所以不妨把用不用PROGMEM 作为模板参数，编译时自动生成两个函数版本，然后分别内联到put_char 和put_bitmap 中，
         * 以多消耗一点ROM 为代价，提高运行效率。
         *
         * @tparam UsePROGMEM     是否从PROGMEM 读取数据
         * @param ptr             数据指针
         * @param grayscale_bit   数据的灰阶位数
         * @param m               是否反色
         */
        template <bool UsePROGMEM>
        void _send_data(const uint8_t *ptr, uint8_t grayscale_bit, ink m) const {
            uint8_t d;

            if constexpr (UsePROGMEM) {
                d = hide::read_pgm_byte(ptr);
            }
            else {
                d = *ptr;
            }

            if (m == ink::inverse) {
                d = ~d;
            }

#if _OLED_BASIC_ENABLE_GRAYSCALE > 0

            if (_screen_grayscale_bit == 1) {
                // 单色屏的快速通道
                _drv->write(d);
            }
            else {
                // 灰阶屏，情况就复杂了
                if (grayscale_bit == 1) {
                    // 单色数据发给灰阶屏，执行灰阶序列扩展
                    _send_grayscale_sequence(d);
                }
                else {
                    // 数据灰阶大于等于屏幕，按屏幕灰阶数发相应数量的数据
                    // 如果数据灰阶小于屏幕，就先按数据灰阶数发几个字节，然后再补发几个0x00
                    uint8_t gg = _screen_grayscale_bit;
                    int8_t extra_count = static_cast<int8_t>(_screen_grayscale_bit) - static_cast<int8_t>(grayscale_bit);
                    if (extra_count > 0) {
                        gg = grayscale_bit;
                    }

                    for (; gg > 0; --gg) {
                        _drv->write(d);
                        ++ptr;

                        if constexpr (UsePROGMEM) {
                            d = hide::read_pgm_byte(ptr);
                        }
                        else {
                            d = *ptr;
                        }

                        if (m == ink::inverse) {
                            d = ~d;
                        }
                    }

                    d = (m == ink::inverse) ? 0xff : 0x00;

                    for (; extra_count > 0; --extra_count) {
                        _drv->write(d);
                    }
                }
            }

#else
            // 只处理单色屏，啥都不判断
            _drv->write(d);
#endif
        }

        // 禁止拷贝
        _MAKE__OBJECT__NOT__COPYABLE(MonoBasic)

       public:
        MonoBasic(Backend &drv, const OledProperty &p, const FontMeta &f) :
            _drv(&drv) {
            set_font(f);
            set_display_property(p);
        }


        void set_display_property(const OledProperty &p) {
            _max_column = p.column_count;
            _max_page = p.page_count;

#if _OLED_BASIC_ENABLE_GRAYSCALE > 0
            _screen_grayscale_bit = p.grayscale_bit;

            // 假定最大灰度8bit == 256 级，最大灰度等级255
            _max_grayscale_level = MAX_GRAYSCALE_LEVEL;

            // 如果屏幕是2bit 灰度，最大灰度等级就是 0xff >> 6 == 3
            _max_grayscale_level = _max_grayscale_level >> (MAX_GRAYSCALE_BIT - p.grayscale_bit);

            // 默认将单色数据按最高灰度等级显示
            _current_grayscale_level = _max_grayscale_level;
#endif
        }

        void set_cursor(PixelIndexType col, PixelIndexType page) {
            _current_column = col;
            _current_page = page;
        }

        void set_cursor(PixelIndexType col) {
            set_cursor(col, _current_page);
        }

        PixelIndexType current_column() const {
            return _current_column;
        }

        PixelIndexType current_page() const {
            return _current_page;
        }

        PixelIndexType increase_column(PixelIndexType inc) {
            _current_column = _column_correct(_current_column + inc);
            return _current_column;
        }

        PixelIndexType line_feed() {
            _current_page = _column_correct(_current_page + _font->height);
            return _current_page;
        }

        void set_font(const FontMeta &f) {
            _font = &f;

            auto w = f.width;
            auto h = f.height;
            auto g = f.grayscale_bit;
            _character_size = w * h * g;
        }


#if _OLED_BASIC_ENABLE_GRAYSCALE > 0

        uint_fast8_t max_grayscale() const {
            return _max_grayscale_level;
        }

        /**
         * @brief 适用于单色字体或bitmap，将其扩展为指定的灰度数据
         *
         *
         * @param g
         */
        void set_grayscale(int_fast8_t g) {
            if (g > _max_grayscale_level) {
                _current_grayscale_level = _max_grayscale_level;
            }
            else {
                _current_grayscale_level = g;
            }
        }

#endif

        void set_num_base(number_base b) {
            if constexpr (LIMIT_NUM_BASE) {
                if (static_cast<int>(b) < 10) {
                    _current_num_base = number_base::n10;
                    return;
                }
            }

            _current_num_base = b;
        }

        /**
         * @brief 设置数字右对齐，在数字左侧填充指定字符。
         *
         * 若数字实际长度大于设置的长度，则无法对齐，按默认的左对齐显示，不填充字符
         *
         * @param num_len
         * @param ch
         */
        void enable_number_right_align(uint8_t num_len, char ch = ' ') {
            if (num_len > NUMBER_LITERAL_WIDTH) {
                num_len = NUMBER_LITERAL_WIDTH;
            }

            _num_length = num_len;
            _num_filling_char = ch;
        }

        /**
         * @brief 设置数字左对齐，在数字右侧填充指定字符。
         *
         * @param num_len
         * @param ch
         */
        void enable_number_left_align(uint8_t num_len, char ch = ' ') {
            if (num_len > NUMBER_LITERAL_WIDTH) {
                num_len = NUMBER_LITERAL_WIDTH;
            }

            _num_length = 0 - num_len;
            _num_filling_char = ch;
        }

        /**
         * @brief 回到默认的左对齐模式，不填充字符
         */
        void disable_number_align() {
            _num_length = 0;
        }

        /**
         * @brief 用一字节单色显示数据填充矩形区域，根据当前设置的灰度扩展为对应灰阶数据
         *
         * @param data
         * @param col_count
         * @param page_count
         */
        void fill(uint8_t data, PixelIndexType col_count, PixelIndexType page_count) {
            auto start_col = _current_column;
            auto start_page = _current_page;

            for (auto pp = page_count; pp > 0; --pp) {
                _drv->set_cursor(start_col, start_page);
                _drv->begin_tx();

                for (auto cc = col_count; cc > 0; --cc) {
                    _send_grayscale_sequence(data);
                }

                _drv->end();
                ++start_page;
                if (start_page > _max_page) {
                    break;
                }
            }

            _update_cursor(col_count, page_count);
        }

        void fill_by_font(uint8_t data, PixelIndexType text_count, PixelIndexType line_count) {
            auto col_count = _font->width * text_count;
            auto page_cont = _font->height * line_count;

            fill(data, col_count, page_cont);
        }

        void clear_screen() {
            set_cursor(0, 0);
            fill(0, _max_column, _max_page);
            set_cursor(0, 0);
        }

        void clear_by_font(PixelIndexType text_count) {
            auto start_col = _current_column;

            clear_text_trail(start_col, text_count);
            set_cursor(start_col);
        }

        void fill_text_trail(uint8_t data, PixelIndexType text_start_col, PixelIndexType max_count) {
            auto text_end_col = max_count * _font->width + text_start_col;

            auto trail_height = _font->height;

            auto trail_start = _current_column;
            auto trail_width = text_end_col - trail_start;

            fill(data, trail_width, trail_height);
        }

        void clear_text_trail(PixelIndexType text_start_col, PixelIndexType max_count) {
            fill_text_trail(0, text_start_col, max_count);
        }

       private:
        /**
         * @brief 适用于AVR 单片机，字体数据放在PROGMEM 里，不能直接用指针读取，所以不能调用put_bitmap
         *
         * @param ch
         * @param m   是否反色
         */
        void _put_char_from_progmem(uint8_t ch, ink m) {
            auto col = _current_column;
            auto page = _current_page;

            ch -= _font->offset;

            const uint8_t *data_ptr = _font->font_data + (_character_size * ch);
            auto w = _font->width;
            auto h = _font->height;

#if _OLED_BASIC_ENABLE_GRAYSCALE > 0
            auto g = _font->grayscale_bit;
#else
            auto g = 1;
#endif

            for (auto hh = h; hh > 0; --hh) {
                _drv->set_cursor(col, page);
                _drv->begin_tx();

                for (auto ww = w; ww > 0; --ww) {
                    _send_data<true>(data_ptr, g, m);
                    data_ptr += g;
                }

                _drv->end();
                ++page;
                if (page > _max_page) {
                    break;
                }
            }

            _update_cursor(w, h);
        }

        /**
         * @brief 把字符数据作为一个bitmap 显示出去
         *
         * @param ch
         * @param m   是否反色
         */
        void _put_char_as_bitmap(uint8_t ch, ink m) {
            ch -= _font->offset;
            const uint8_t *data_ptr = _font->font_data + (_character_size * ch);

            BitmapMeta bh;
            bh.bitmap = data_ptr;
            bh.width = _font->width;
            bh.height = _font->height;
            bh.grayscale_bit = _font->grayscale_bit;

            put_bitmap(bh, m);
        }

       public:
        /**
         * @brief 在当前cursor 位置显示一个字符，然后将cursor 向后移动
         *
         * @param ch
         * @param m   是否反色
         */
        void put_char(uint8_t ch, ink m = ink::none) {
            if (m == ink::none) {
                m = _current_ink;
            }

            uint8_t spacing = 0x00;
            // 空格就是在屏幕上放一个矩形，没反色就放个空白矩形
            if (m == ink::inverse) {
                spacing = 0xff;
            }

            switch (ch) {
                case ' ':
                    fill_by_font(spacing, 1, 1);
                    break;

                case '\r':
                    set_cursor(0);
                    break;

                case '\n':
                    set_cursor(0);
                    line_feed();
                    break;

                default:
#ifdef __AVR_ARCH__
                    _put_char_from_progmem(ch, m);

#else
                    _put_char_as_bitmap(ch, m);
#endif
            }

            if (_char_spacing > 0) {
                fill(spacing, _char_spacing, _font->height);
            }
        }

        void put_bitmap(const BitmapMeta &bh, ink m = ink::none) {
            if (m == ink::none) {
                m = _current_ink;
            }

            auto col = _current_column;
            auto page = _current_page;

            const uint8_t *data_ptr = bh.bitmap;
            auto w = bh.width;
            auto h = bh.height;

#if _OLED_BASIC_ENABLE_GRAYSCALE > 0
            auto g = bh.grayscale_bit;
#else
            auto g = 1;
#endif

            for (; h > 0; --h) {
                _drv->set_cursor(col, page);
                _drv->begin_tx();

                for (auto ww = w; ww > 0; --ww) {
                    _send_data<false>(data_ptr, g, m);
                    data_ptr += g;
                }

                _drv->end();
                ++page;
                if (page > _max_page) {
                    break;
                }
            }

            _update_cursor(w, h);
        }

        /**
         * @brief 显示字符串，可以自动处理换行
         *
         * @param str
         * @param m
         */
        void put_str(const char *str, ink m = ink::none) {
            if (str == nullptr) {
                return;
            }
            while (*str) {
                uint8_t ch = *str;
                put_char(ch, m);
                ++str;
            }
        }

        /**
         * @brief 显示不以\0 结尾的字符串
         *
         * @param str
         * @param m
         */
        void put_str(const char *str, size_t len, ink m = ink::none) {
            if (str == nullptr) {
                return;
            }
            for (; len > 0; --len) {
                uint8_t ch = *str;
                if (ch) {
                    put_char(ch, m);
                    ++str;
                }
                else {
                    break;
                }
            }
        }

        /**
         * @brief 显示无符号整数
         *
         * @param n
         * @param m
         * @param prefix
         */
        void put_unsigned(UnsignedType n, ink m = ink::none, char prefix = 0) {
            uint8_t b = static_cast<uint8_t>(_current_num_base);

            char *str_ptr = &_number_buffer[NUMBER_BUFFER_SIZE - 1];
            *str_ptr = '\0';

            int_fast8_t counter = 0;
            do {
                --str_ptr;
                ++counter;

                auto c = n % b;
                n /= b;
                auto ch = static_cast<uint8_t>((c < 10) ? (c + '0') : (c + 'A' - 10));
                *str_ptr = ch;
            } while (n > 0);

            if (prefix > 0) {
                --str_ptr;
                ++counter;
                *str_ptr = prefix;
            }

            // 处理左右对齐
            if (_num_length > counter) {
                // 如果数字长度比_num_length 短，表示需要右对齐，在左侧填充指定字符或空格
                counter = _num_length - counter;
                for (; counter > 0; --counter) {
                    --str_ptr;
                    *str_ptr = _num_filling_char;
                }
            }

            put_str(str_ptr, m);

            if (_num_length < 0) {
                // 左对齐，在右侧填充字符
                counter = 0 - _num_length - counter;

                if (counter > 0) {
                    for (; counter > 0; --counter) {
                        put_char(_num_filling_char, m);
                    }
                }
            }
        }

        /**
         * @brief 显示有符号整数
         *
         * @param n
         * @param m
         */
        void put_signed(SignedType n, ink m = ink::none) {
            if (n < 0) {
                n = -n;
                put_unsigned(n, m, '-');
                // 整数函数只能处理无符号整数，但是可以给数字设置一个字符的前缀，用这个前缀实现负号。
                // 用前缀而不是简单的打印个负号，因为处理左、右对齐的时候，负号要和数字部分一起移动。
            }
            else {
                put_unsigned(n, m);
            }
        }

        /**
         * @brief 打印浮点
         *
         * @param f
         * @param precision  保留几位小数
         * @param m
         */
        void put_float(float f, ink m = ink::none) {
            using namespace std;
            if (isnan(f)) {
                put_str("NAN");
                return;
            }

            if (isinf(f)) {
                put_str("INF");
                return;
            }

            uint8_t prefix = 0;
            if (f < 0) {
                f = -f;
                prefix = '-';
            }

            if (f > (static_cast<float>(UNSIGNED_MAX))) {
                put_str("OVF");
                return;
            }

            // assert(_multiply_factor < (UNSIGNED_MAX / 10));
            // 四舍五入
            f += _round_bit;

            // 取整
            uint32_t left_part = static_cast<uint32_t>(f);
            f -= left_part;
            f *= _multiply_factor;
            uint32_t right_part = static_cast<uint32_t>(f);

            auto prev_num_len = _num_length;
            auto prev_filling = _num_filling_char;
            // TDOO: 处理左右对齐
            // 可以设定，左右对齐的长度只影响小数的整数部分，
            // 小数部分总是粘在整数部分右边。
            // 所以右对齐时不需要额外处理，只有左对齐时，需要把小数部分的显示位置左移
            put_unsigned(left_part, m, prefix);
            // 小数点右边
            // 打印整数时默认会忽略前导零，而小数的前导零不能忽略
            // 临时设置数字格式为右对齐，长度为精度，
            // 再把填充字符设为'0'，把前导零补上
            _num_length = _precision;
            _num_filling_char = '0';

            put_char('.', m);             // 小数点
            put_unsigned(right_part, m);  // 至少会显示1 位小数

            _num_length = prev_num_len;
            _num_filling_char = prev_filling;
        }

        /**
         * @brief 设置显示浮点的小数位数，最少一位
         *
         * @param p
         */
        void set_float_precision(uint8_t p) {
            switch (p) {
                case 0:
                    p = 1;
                case 1:
                    _multiply_factor = 10.0;
                    _round_bit = 0.5 / 10;
                    break;
                case 2:
                    _multiply_factor = 100.0;
                    _round_bit = 0.5 / 100;
                    break;
                case 3:
                    _multiply_factor = 1000.0;
                    _round_bit = 0.5 / 1000;
                    break;
                case 4:
                    _multiply_factor = 10000.0;
                    _round_bit = 0.5 / 10000;
                    break;
                default:
                    _multiply_factor = 1.0;
                    for (auto i = p; i > 0; --i) {
                        _multiply_factor *= 10.0;
                    }
                    _round_bit = 0.5 / _multiply_factor;
            }
            _precision = p;
        }

        void set_ink(ink m) {
            _current_ink = m;
        }

        void reset_ink() {
            _current_ink = ink::none;
        }
    };

}  // namespace oled_basic